#pragma once
#include "DXUT.h"
#include "MeshRender.h"
#include "..\\Utility\\D3D\\D3DUtilityFunction.h"
#include "DXUTMesh.h"
#include "D3DX9MeshToD3D11Mesh.h"
#include <vector>


template<class CVertType>
class CXFileLoader:public CMeshRender
{
public:
	CXFileLoader(void):CMeshRender()
	{
		ZeroMemory( m_strMediaDir, sizeof( m_strMediaDir ) );
		STRIDE_SIZE = CVertType::STRIDE_SIZE;
		m_bOptimize = true;
		m_iVType = CVertType::VType;
	}
	virtual ~CXFileLoader(void)
	{
		OnD3D11DestroyDevice(NULL);
	}
	void Destroy()
	{
		for( int iMaterial = 0; iMaterial < m_Materials.size(); iMaterial++ )
		{
			Material* pMaterial = m_Materials[iMaterial];

			// Avoid releasing the same texture twice
			for( int x = iMaterial + 1; x < m_Materials.size(); x++ )
			{
				Material* pCur = m_Materials[x];
				if( pCur->pTexture == pMaterial->pTexture )
					pCur->pTexture = NULL;
			}

			SAFE_RELEASE( pMaterial->pTexture );
			SAFE_DELETE( pMaterial );
		}

		m_Materials.clear();		
		m_Subsets.clear();
	}
	virtual HRESULT OnD3D11CreateDevice( ID3D11Device* pd3dDevice, ID3DX11EffectTechnique* pTech, 
		const DXGI_SURFACE_DESC* pBackBufferSurfaceDesc, CDirectoryUtil* pDirUtil, void* pUserContext )
	{
		HRESULT hr = S_OK;

		if(m_pVB == NULL || m_pIB == NULL || !m_bModelReady)
		{
			printf("No geometry data loaded yet!\n");
#ifdef _DEBUG
			OUTPUTERRORINFO;
#endif
			return E_FAIL;
		}

		//Create vertex layout
		V_RETURN(CD3DUtilityFunction::CreateVertexLayout(pd3dDevice,CVertType::layout,ARRAYSIZE(CVertType::layout),pTech,&m_pVertexLayout));
		
		return hr;
	}

	virtual void OnD3D11DestroyDevice( void* pUserContext )
	{
		CMeshRender::OnD3D11DestroyDevice(pUserContext);
		Destroy();
	}

	virtual void Render( ID3D11Device* pd3dDevice, ID3D11DeviceContext* pd3dImmediateContext, ID3DX11Effect* pEffect, 
		ID3DX11EffectTechnique* pRenderTech, enum RENDERMESHTECH iMeshType, const D3DXMATRIX& mWorldCamera, double fTime, 
		float fElapsedTime, void* pUserContext )
	{
		if(iMeshType != RENDERMESHTECH::RENDERSTATICMESH)
		{
#ifdef _DEBUG
			OUTPUTERRORINFO;
#endif
				return;
		}

		HRESULT hr;
		//Set model world matrix
		D3DXMATRIX mWorldMatrix = m_mWorldModel * mWorldCamera;
		hr = (pEffect->GetVariableByName("mWorld")->AsMatrix()->SetMatrix((float*)(&mWorldMatrix)));
		//HRESULT hr = S_OK;
		// Set render resources
		pd3dImmediateContext->IASetInputLayout( m_pVertexLayout );
		ID3D11Buffer* pVB[1] = { m_pVB };
		UINT pStride[1] = {STRIDE_SIZE};
		UINT pOffset[1] = { 0 };
		pd3dImmediateContext->IASetVertexBuffers( 0, 1, pVB, pStride, pOffset );
		if(m_b32bitIndex)
			pd3dImmediateContext->IASetIndexBuffer(m_pIB, DXGI_FORMAT_R32_UINT, 0);
		else
			pd3dImmediateContext->IASetIndexBuffer(m_pIB, DXGI_FORMAT_R16_UINT, 0);
		pd3dImmediateContext->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

		// Render objects here...
		// Apply the technique contained in the effect 
		D3DX11_TECHNIQUE_DESC Desc;
		pRenderTech->GetDesc( &Desc );
		ID3DX11EffectVectorVariable* hAmbient = pEffect->GetVariableByName("vAmbientColor")->AsVector();
		ID3DX11EffectVectorVariable* hDiffuse = pEffect->GetVariableByName("vDiffuseColor")->AsVector();
		ID3DX11EffectVectorVariable* hSpecular = pEffect->GetVariableByName("vSpecularColor")->AsVector();
		ID3DX11EffectScalarVariable* hSpecularPower = pEffect->GetVariableByName("fSpecularPower")->AsScalar();
		ID3DX11EffectShaderResourceVariable* hDiffuseTex = pEffect->GetVariableByName("texDiffuse")->AsShaderResource();
		ID3DX11EffectScalarVariable* hbTex = pEffect->GetVariableByName("bTexture")->AsScalar();

		for( UINT iPass = 0; iPass < Desc.Passes; iPass++ )
		{
			for (unsigned int iSubset = 0; iSubset < m_Subsets.size(); iSubset++)
			{
				//find the corresponding material index for current subset!
				unsigned int iMatID = m_Subsets[iSubset].MatIndex;
				if(hAmbient)
				{	
					D3DXVECTOR4 ambient = D3DXVECTOR4(m_Materials[iMatID]->vAmbient,1.0f);
					hAmbient->SetFloatVector((float*)(&ambient));
				}
				if(hDiffuse)
				{	
					D3DXVECTOR4 diffuse = D3DXVECTOR4(m_Materials[iMatID]->vDiffuse,1.0f);
					hDiffuse->SetFloatVector((float*)(&diffuse));
				}
				if(hSpecular)
				{	
					D3DXVECTOR4 specular= D3DXVECTOR4(m_Materials[iMatID]->vSpecular,1.0f);
					hSpecular->SetFloatVector((float*)(&specular));
				}
				if(hSpecularPower)
					hSpecularPower->SetFloat(m_Materials[iMatID]->fShininess);

				if(hDiffuseTex&&m_Materials[iMatID]->pTexture)
					{
						hDiffuseTex->SetResource(m_Materials[iMatID]->pTexture);
						hbTex->SetBool(TRUE);
					}
				else 
					hbTex->SetBool(FALSE);

				pRenderTech->GetPassByIndex( iPass )->Apply( 0, pd3dImmediateContext );
				pd3dImmediateContext->DrawIndexed( m_Subsets[iSubset].FaceCount*3, m_Subsets[iSubset].FaceStart*3, 0 );			
			}
		}
	}

	virtual bool LoadModel(const char* strFileName, ID3D11Device* pd3dDevice)
	{
		HRESULT hr = S_OK;
		Destroy();
		std::string sFileName;
		sFileName.assign(strFileName);
		std::wstring wsFileName = UtilityFunction::strtowstr(sFileName);
		//create d3d9 device
		LPDIRECT3DDEVICE9 pDev9 = NULL;
		CD3DUtilityFunction::CreateD3D9Device(&pDev9);

		//CDXUTMesh read X file data
		CDXUTMesh tMesh;		
		V( tMesh.Create( pDev9, wsFileName.data(), m_bOptimize ) );
		if(!(tMesh.GetMesh()->GetOptions()&D3DXMESH_32BIT))
			m_b32bitIndex = false;

		//get bytes per vertex
		DWORD dwBytesPerVertex = tMesh.m_dwBytesPerVertex;		
		if (dwBytesPerVertex != STRIDE_SIZE)
		{
			printf("STRIDE_SIZE is incorrect!\n");
#ifdef _DEBUG
			OUTPUTERRORINFO;
#endif

			tMesh.Destroy();
			SAFE_RELEASE( pDev9 );
			return false;
		}
		//convert to DX10 mesh data
		SAFE_RELEASE(m_pVB);
		SAFE_RELEASE(m_pIB);
		m_Subsets.clear();
		XMeshConv::CreateD3D11MeshFromD3DX9Mesh(tMesh.GetMesh(),pd3dDevice, &m_pVB, &m_pIB, m_Subsets);

		//copy materials
		m_Materials.clear();
		DWORD NumMaterials= tMesh.m_dwNumMaterials;
		if(NumMaterials>0)
		{
			WCHAR m_strPathW[MAX_PATH];
			V( DXUTFindDXSDKMediaFileCch( m_strPathW, sizeof(m_strPathW) / sizeof(WCHAR), wsFileName.data() ));
			WCHAR *pLastBSlash = wcsrchr( m_strPathW, L'\\' );
			if( pLastBSlash )
				*(pLastBSlash + 1) = L'\0';
			else
				*m_strPathW = L'\0';
			CHAR m_strPath[MAX_PATH];
			//change to multi-byte format
			WideCharToMultiByte( CP_ACP, 0, m_strPathW, -1, m_strPath, MAX_PATH, NULL, FALSE );
			CHAR sTexName[MAX_PATH];
			//create context
			ID3D11DeviceContext* pContext = NULL;
			pd3dDevice->GetImmediateContext(&pContext);
			for(DWORD i=0;i<NumMaterials;i++)
			{	
				Material* pMat = new Material();				
				swprintf_s(pMat->strName, L"Material%d", i);
				//ambient
				pMat->vAmbient.x = tMesh.m_pMaterials[i].Ambient.r;
				pMat->vAmbient.y = tMesh.m_pMaterials[i].Ambient.g;
				pMat->vAmbient.z = tMesh.m_pMaterials[i].Ambient.b;
				//diffuse
				pMat->vDiffuse.x = tMesh.m_pMaterials[i].Diffuse.r;
				pMat->vDiffuse.y = tMesh.m_pMaterials[i].Diffuse.g;
				pMat->vDiffuse.z = tMesh.m_pMaterials[i].Diffuse.b;
				//specular
				pMat->vSpecular.x = tMesh.m_pMaterials[i].Specular.r;
				pMat->vSpecular.y = tMesh.m_pMaterials[i].Specular.g;
				pMat->vSpecular.z = tMesh.m_pMaterials[i].Specular.b;

				if(pMat->vSpecular.x > 0 || pMat->vSpecular.y > 0 || pMat->vSpecular.z > 0)
					pMat->bSpecular = true;
				pMat->fShininess = tMesh.m_pMaterials[i].Power;
				//read texture one by one                      
				CHAR* pCurTexName = tMesh.m_strMaterials[i];
				if((*pCurTexName)!=NULL)
				{
					memset(sTexName,0,sizeof(CHAR)*MAX_PATH);
					sprintf(sTexName, "%s%s", m_strPath, pCurTexName);            
					std::string strTexName;
					strTexName.assign(sTexName);
					std::wstring wsTexName = UtilityFunction::strtowstr(strTexName);
					wcscpy_s(pMat->strTexture, wsTexName.data());
					
					if( FAILED(DXUTGetGlobalResourceCache().CreateTextureFromFile( pd3dDevice, pContext, sTexName, &(pMat->pTexture)) ) )
						return false;
				}
				else
					pMat->pTexture = NULL;

				m_Materials.push_back(pMat);
			}
			//release context
			SAFE_RELEASE(pContext);
		}
		else if(NumMaterials == 0)
		{
			Material* pMaterial = new Material();
			if( pMaterial == NULL )
				return false;

			CD3DUtilityFunction::InitMaterial( pMaterial );
			wcscpy_s( pMaterial->strName, MAX_PATH - 1, L"default" );
			m_Materials.push_back( pMaterial );
		}
		//computing the bounding sphere
		BYTE* v = 0;
		tMesh.GetMesh()->LockVertexBuffer(0,(void**)&v);
		D3DVERTEXELEMENT9 origin_decl[MAX_FVF_DECL_SIZE];
		tMesh.GetMesh()->GetDeclaration(origin_decl);
		hr = D3DXComputeBoundingSphere((D3DXVECTOR3*)v,tMesh.GetMesh()->GetNumVertices(),D3DXGetDeclVertexSize(origin_decl,0),&m_vCenter,&m_fRadius);
		tMesh.GetMesh()->UnlockVertexBuffer();
		if( FAILED(hr) )
		{
			return false;
		}
		//delete the d3d9 mesh and d3d9device
		tMesh.Destroy();
		SAFE_RELEASE( pDev9 );

		m_bModelReady = true;

		return m_bModelReady;
	}
public:
	std::vector <Material*> m_Materials;     // Holds material properties per subset
	std::vector <MESH_ATTRIBUTE_RANGE> m_Subsets; //Subset mesh for rendering
	WCHAR   m_strMediaDir[ MAX_PATH ];               // Directory where the mesh was found
	bool m_bOptimize;
};

